<?php

class Websocket
{
    private $sockets; // 所有socket连接池
    private $users; // 所有连接用户
    private $server; // 服务端 socket

    public function __construct($ip, $port)
    {
        $this->server = socket_create(AF_INET, SOCK_STREAM, 0);
        $this->sockets = [$this->server];
        $this->users = [];
        socket_bind($this->server, $ip, $port);
        socket_listen($this->server, 3);
        echo "server start at {$ip}:{$port}", PHP_EOL;
    }

    public function run()
    {
        $write = null;
        $except = null;
        while (true) {
            $active_sockets = $this->sockets;
            socket_select($active_sockets, $write, $except, null);
            foreach ($active_sockets as $socket) {
                if ($socket == $this->server) {
                    $user = socket_accept($this->server);
                    $key = uniqid();
                    $this->sockets[] = $user;
                    $this->users[$key] = [
                        'socket' => $user,
                        'handshake' => false,
                    ];
                } else {
                    $buffer = '';
                    $bytes = socket_recv($socket, $buffer, 1024, 0);
                    $key = $this->findUserBySocket($socket);
                    if ($bytes == 0) {
                        $this->disconnect($socket);
                    } else {
                        // var_dump($this->users);
                        if (!$this->users[$key]['handshake']) {
                            $this->handshake($key, $buffer);
                        } else {
                            $msg = $this->decodeMsg($buffer);
                            $replyMsg = $this->encodeMsg($msg);
                            socket_write($socket, $replyMsg, strlen($replyMsg));
                        }
                    }
                }
            }
        }
    }

    private function disconnect($socket)
    {
        $key = $this->findUserBySocket($socket);
        unset($this->users[$key]);
        foreach ($this->sockets as $k => $v) {
            if ($v == $socket) {
                unset($this->sockets[$k]);
            }
            // socket_shutdown($socket);
            // socket_close($socket);
        }
    }

    private function findUserBySocket($socket)
    {
        foreach ($this->users as $key => $user) {
            if ($user['socket'] == $socket) {
                return $key;
            }
        }
        return -1;
    }

    private function handshake($k, $buffer)
    {
        $buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
        $key = trim(substr($buf, 0, strpos($buf, "\r\n")));
        $newKey = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));
        $newMsg = "HTTP/1.1 101 Switching Protocols\r\n";
        $newMsg .= "Upgrade: websocket\r\n";
        $newMsg .= "Sec-WebSocket-Version: 13\r\n";
        $newMsg .= "Connection: Upgrade\r\n";
        $newMsg .= "Sec-WebSocket-Accept: {$newKey}\r\n\r\n";
        socket_write($this->users[$k]['socket'], $newMsg, strlen($newMsg));
        $this->users[$k]['handshake'] = true;
        return true;
    }

    private function encodeMsg($buffer)
    {
        $len = strlen($buffer);
        if ($len <= 125) {
            return "\x81" . chr($len) . $buffer; 
        } elseif ($len <= 65535) {
            return "\x81" . chr(126) . pack("n", $len) . $buffer;
        } else {
            return "\x81" . chr(127) . pack("xxxxN", $len) . $buffer;
        }
    }

    private function decodeMsg($buffer)
    {
        $len = $masks = $data = $decoded = null;
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }
        return $decoded;
    }
}

$ws = new Websocket('127.0.0.1', 1234);
$ws->run();
